﻿using System;
using SdmxMl.Common;
using SdmxMl.GenericMetadata;
using SdmxMl.Manager;
using SdmxMl.Structure;
using Ch.Admin.Bfs.Sms.Sdmx.Extension.CodeBook.Configuration;

namespace Ch.Admin.Bfs.Sms.Sdmx.Extension.CodeBook.Meta
{
    public class OverviewProvider : BaseMetaProvider, IMetaProvider
    {
        /// <summary> Loader, container for SDMX artefacts </summary>
        private MetadataSetType metaSet;
        private MetadataStructureDefinitionType msd;
        private ReportStructureType report;

        public OverviewProvider(string lang, string metaFullId, BookConfiguration cfg)
            : base(lang, metaFullId, cfg)
        {
            try
            {
                // Get metadataset (DDI like)
                ArtefactRefTyped art = new ArtefactRefTyped(SdmxArtefactType.MetadataSet, fullIdKey);
                sm.LoadMissingArtefactsFromRegistry(art);
                metaSet = sm.GetArtefact(art) as SdmxMl.GenericMetadata.MetadataSetType;

                // Get MSD
                sm.LoadMissingArtefactsFromRegistry(metaSet.MetadataStructureRef);
                msd = sm.GetArtefact(metaSet.MetadataStructureRef) as MetadataStructureDefinitionType;
                report = msd.ReportStructureList.LocateItem(metaSet.ReportRef);
            }
            catch (Exception ex)
            {
                throw new ApplicationException("Unable to obtain related metadata for " + fullIdKey, ex);
            }

            GetOverviewHierarchy();
        }

        private string GetTitle(string titleTag)
        {
            AttributeValueSetType atv = metaSet.AttributeValueSetList[0];
            ReportedAttributeType rat = atv.ReportedAttributeList.GetReportedAttributeOfConcept(titleTag);
            string title = "unknown";
            if (rat != null && rat.Value.GetForLanguage(lg).Length > 0)
                title = rat.Value.GetForLanguage(lg);
            return title;
        }

        private void GetOverviewHierarchy()
        {
            // We have only one row
            AttributeValueSetType atv = metaSet.AttributeValueSetList[0];
            hierarchy = new Hierarchy();

            //We expect two list of reportedAttributes
            if (atv.ReportedAttributeList.Count < 2)
                throw new ApplicationException("Invalid Domains count for Codebook overview");
            // create Root and two domains children
            HierarchyNode root = new HierarchyNode(metaSet.Id, GetTitle("titl"), metaSet);
            HierarchyNode overviewDomain = MakeRatNode(atv.ReportedAttributeList[1]);
            hierarchy.Add(root);

            // Prepare Impressum
            root.Nodes.Add(PrepareImpressum(atv.ReportedAttributeList[0]));

            // Prepare General overview
            root.Nodes.Add(overviewDomain);
            foreach (ReportedAttributeType rat in atv.ReportedAttributeList[1].ReportedAttributeList)
            {
                // Ignore attribute at top level if no content defined
                if (HasSubContent(rat, lg))
                {
                    HierarchyNode node = MakeRatNode(rat);
                    overviewDomain.Nodes.Add(node);
                    RecurseReportedAttribute(rat, node);
                }
            }
        }

        /// <summary>
        /// Impressum provides detected information as a flat list of nodes, holding associated concept name and reported attribute value
        /// </summary>
        private HierarchyNode PrepareImpressum(ReportedAttributeType ratImpressum)
        {
            HierarchyNode rootImpressum = new HierarchyNode("Impressum", string.Empty, null);
            foreach (ReportedAttributeType rat in ratImpressum.ReportedAttributeList)
            {
                RecurseImpressum(rootImpressum, rat);
            }

            return rootImpressum;
        }

        private void RecurseImpressum(HierarchyNode rootImpressum, ReportedAttributeType rat)
        {
            if (rat.Value != null && rat.Value.IsEmpty == false)
                rootImpressum.Nodes.Add(new HierarchyNode(GetConceptName(rat), rat.Value.GetForLanguage(this.lg), null));
            foreach (ReportedAttributeType ratChild in rat.ReportedAttributeList)
            {
                RecurseImpressum(rootImpressum, ratChild);
            }
        }

        private HierarchyNode MakeRatNode(ReportedAttributeType rat)
        {
            HierarchyNode nd = new HierarchyNode(rat.ConceptID, rat.Value.GetForLanguage(lg), GetConceptName(rat), rat);
            nd.HasDetail = HasSubContent(rat, lg);
            return nd;
        }

        private void RecurseReportedAttribute(ReportedAttributeType rep, HierarchyNode node)
        {
            foreach (ReportedAttributeType rat in rep.ReportedAttributeList)
            {
                HierarchyNode nodeChild = MakeRatNode(rat);
                    node.Nodes.Add(nodeChild);
                    RecurseReportedAttribute(rat, nodeChild);
            }
        }

        private bool HasSubContent(ReportedAttributeType rat, string lg)
        {
            foreach (ReportedAttributeType rep in rat.ReportedAttributeList)
            {
                if (rep.Value.GetForLanguage(lg).Length > 0)
                    return true;
                if (HasSubContent(rep, lg))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// From the ddi reported attribute, fetch corresponding name in working language
        /// </summary>
        /// <param name="rpt">the targeted reported attribute</param>
        /// <returns> Name of associated concept in working language</returns>
        private string GetConceptName(ReportedAttributeType rpt)
        {
            string txt = "Not found";
            if (rpt != null)
            {
                MetadataAttributeType meta = report.MetadataAttributeList.GetAttributeReferingConcept(rpt.ConceptID);
                ConceptType concept = refHelper.GetConcept(rpt.ConceptID, meta.ConceptSchemeInfo);
                txt = concept.Name.GetForLanguage(lg);
            }
            return txt;
        }


    }
}
